/**
 * 
 */
package os.filesystem;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;

import os.filesystem.exception.FSMalformedCommandException;
import os.filesystem.tool.Argument;
import os.filesystem.tool.Command;

/**
 * Given a string from the command line, this utility class will parse the
 * string and convert the arguments to a list of {@link Argument} objects.
 * <p>
 * 
 * @author Raghav Sidhanti
 * @since Mar 8, 2012 12:52:30 PM
 * 
 */
public class CommandLineParser {

	private static final String ARG_DELIMITER = " ";

	public static class CommandImpl implements Command {

		private List<Argument> arguments = new ArrayList<Argument>(0);
		private Argument commandName;
		private List<Argument> options = new LinkedList<Argument>();
		private List<Argument> nonOptions = new LinkedList<Argument>();

		public CommandImpl(List<Argument> arguments) {
			if (arguments == null) {
				this.arguments = new ArrayList<Argument>(0);
			}

			if (!arguments.isEmpty()) {
				this.arguments = arguments;
				this.commandName = arguments.get(0);

				for (int index = 1; index < arguments.size(); index++) {
					Argument arg = arguments.get(index);
					if (arg.isOption()) {
						options.add(arg);
					} else {
						nonOptions.add(arg);
					}
				}
			} else {
				this.commandName = new ArgumentImpl("", false);
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see os.filesystem.tool.Command#getCommandArgument()
		 */
		public Argument getCommandNameArgument() {
			return commandName;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see os.filesystem.tool.Command#getArguments()
		 */
		public List<Argument> getArguments() {
			return arguments;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see os.filesystem.tool.Command#getOptions()
		 */
		public List<Argument> getOptionArguments() {
			return options;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see os.filesystem.tool.Command#getNonOptions()
		 */
		public List<Argument> getNonOptionArguments() {
			return nonOptions;
		}
	}

	public static class ArgumentImpl implements Argument {

		private String argument = "";
		private boolean isOption = false;

		public ArgumentImpl(String arg, boolean isOption) {
			this.argument = arg;
			this.isOption = isOption;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see os.filesystem.tool.Argument#getArgument()
		 */
		public String getArgument() {
			return argument;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see os.filesystem.tool.Argument#isOption()
		 */
		public boolean isOption() {
			return isOption;
		}
	}

	/**
	 * This method will convert the command string to a list of {@link Argument}
	 * objects.
	 * <p>
	 * The returned list will preserve the order of the arguments as in the
	 * input string.
	 * 
	 * @param cmd
	 * @return
	 * @throws FSMalformedCommandException
	 */
	private static final Character OPTION_INDICATOR = '-';

	@SuppressWarnings("unchecked")
	public static Command parse(String cmd) throws FSMalformedCommandException {
		Queue<Argument> argQ = new LinkedList<Argument>();
		StringTokenizer st = new StringTokenizer(cmd, ARG_DELIMITER);
		while (st.hasMoreTokens()) {
			String str = st.nextToken().trim();
			if (!str.isEmpty()) {
				Argument arg;
				if (str.startsWith(OPTION_INDICATOR.toString())) {
					// arg is an option
					// Cases: -a, --a, -a-b, -abb-, -, ---
					boolean isMalformedOption = false;
					if (str.length() > 1) {
						for (int index = 1; index < str.length(); index++) {
							if (OPTION_INDICATOR == str.charAt(index)) {
								isMalformedOption = true;
								break;
							}
						}
					} else {
						isMalformedOption = true;
					}

					if (isMalformedOption) {
						throw new FSMalformedCommandException("Malformed option: " + str);
					} else {
						arg = new ArgumentImpl(str.substring(1), true);
					}
				} else {
					// arg is not option
					arg = new ArgumentImpl(str, false);
				}
				argQ.add(arg);
			}
		}
		return new CommandImpl((List<Argument>) argQ);
	}
}
